#include "xlcomm/net/detail/socket.h"

#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <chrono>
#include <thread>

#include "xlcomm/base/logger.h"
#include "xlcomm/net/endian.h"

namespace xlcomm {
namespace net {

Socket::Socket(int sockfd) : sockfd_(sockfd) {}

Socket::~Socket() {
  if (::close(sockfd_) < 0) {
    LOG_SYSERROR(close) << "sockfd:" << sockfd_;
  }
}

void Socket::Bind(const InetAddress& local_addr) {
  const struct sockaddr* sockadd = local_addr.ToSockaddr();
  if (::bind(sockfd_, sockadd, sizeof(*sockadd)) < 0) {
    LOG_SYSFATAL(bind) << "IP:" << local_addr.ToIp()
                       << " port:" << local_addr.Port();
  }
}

void Socket::Listen() {
  if (::listen(sockfd_, SOMAXCONN) < 0) {
    LOG_SYSFATAL(listen) << "sockfd:" << sockfd_;
  }
}

int Socket::Accept(InetAddress* peer_addr) {
  struct sockaddr addr;
  memset(&addr, 0, sizeof(addr));
  socklen_t len = sizeof(addr);
  int connfd = ::accept4(sockfd_, &addr, &len, SOCK_NONBLOCK | SOCK_CLOEXEC);
  if (connfd < 0) {
    int savedErrno = errno;
    LOG_SYSERROR(accept4) << "sockfd:" << sockfd_;
    switch (savedErrno) {
      case EAGAIN:
      case ECONNABORTED:
      case EINTR:
      case EPROTO:
      case EPERM:
      case EMFILE:
        errno = savedErrno;
        break;
      case EBADF:
      case EFAULT:
      case EINVAL:
      case ENFILE:
      case ENOBUFS:
      case ENOMEM:
      case ENOTSOCK:
      case EOPNOTSUPP:
        LOG_FATAL << "unexpected error of accept " << savedErrno;
        break;
      default:
        LOG_FATAL << "unknown error of accept " << savedErrno;
        break;
    }
  }
  peer_addr->FromSockaddr(addr);
  return connfd;
}

bool Socket::ShutdownWrite() {
  if (::shutdown(sockfd_, SHUT_WR) < 0) {
    LOG_SYSERROR(shutdown) << "sockfd:" << sockfd_;
    return false;
  }
  return true;
}

bool Socket::SetTcpNoDelay(bool on) {
  int opt = on ? 1 : 0;
  if (::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &opt,
                   static_cast<socklen_t>(sizeof(opt))) < 0) {
    LOG_SYSERROR(setsockopt) << "sockfd:" << sockfd_;
    return false;
  }
  return true;
}

bool Socket::SetReuseAddr(bool on) {
  int opt = on ? 1 : 0;
  if (::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt,
                   static_cast<socklen_t>(sizeof(opt))) < 0) {
    LOG_SYSERROR(setsockopt) << "sockfd:" << sockfd_;
    return false;
  }
  return true;
}

bool Socket::SetReusePort(bool on) {
#ifdef SO_REUSEPORT
  int opt = on ? 1 : 0;
  int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &opt,
                         static_cast<socklen_t>(sizeof(opt)));
  if (ret < 0 && on) {
    LOG_SYSERROR(setsockopt) << "sockfd:" << sockfd_;
    return false;
  }
  return true;
#else
  if (on) {
    LOG_ERROR << "SO_REUSEPORT is not supported.";
    return false;
  }
  return true;
#endif
}

bool Socket::SetKeepAlive(bool on) {
  int opt = on ? 1 : 0;
  if (::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &opt,
                   static_cast<socklen_t>(sizeof(opt))) < 0) {
    LOG_SYSERROR(setsockopt) << "sockfd:" << sockfd_;
    return false;
  }
  return true;
}

}  // namespace net
}  // namespace xlcomm